/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						       hero.h

	$Header: /game/hero.h $

	$NoKeywords: $

 ************************************************************************/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "hero.h"

#include <algorithm>

#include "abstract_script_action.h"
#include "adventure_frame.h"
#include "adventure_map_window.h"
#include "adv_actor_action.h"
#include "adventure_ai.h"
#include "adv_object_type.h"
#include "adventure_map.h"
#include "ai_importance.h"
#include "ai_value.h"
#include "adventure_sounds.h"
#include "army.h"
#include "artifact_properties.h"
#include "artifact_type.h"
#include "automatic_spells.h"
#include "basic_dialog.h"
#include "bitset_io.h"
#include "combat_model_window.h"
#include "combat_sounds.h"
#include "creature_ability.h"
#include "creature_traits.h"
#include "default_artifacts.h"
#include "elements_of.h"
#include "enum_operations.h"
#include "external_string.h"
#include "format_string.h"
#include "game_replace_keywords.h"
#include "get_artifact_damage_modifier.h"
#include "carryover_data.h"
#include "hero_class.h"
#include "hero_class_properties.h"
#include "hero_keyword_replacer.h"
#include "hero_portrait.h"
#include "missile_type.h"
#include "music.h"
#include "ownable_event.h"
#include "player.h"
#include "random.h"
#include "replace_keywords.h"
#include "scriptable_event.h"
#include "simple_dialog.h"
#include "single_use_object.h"
#include "sized_int_types.h"
#include "skill_choice_dialog.h"
#include "skill_properties.h"
#include "spell_properties.h"
#include "streambuf_operators.h"
#include "tactics_bonus.h"
#include "town.h"
#include "town_ptr.h"
#include "script_targeting.h"

int const k_tactics_defense_bonus[]		= {  0,  10, 20, 30, 40, 50 };
int const k_tactics_offense_bonus[]		= {  0,  10, 20, 30, 40, 50 };
int const k_tactics_speed[]				= {  0,   1,  2,  3,  4,  5 };
int const k_leadership_morale_bonus[]	= {  0,   1,  2,  3,  4, 5 };
int const k_tactics_move_bonus[]		= {  0, 300, 450, 600, 750, 900 };
int const k_leadership_luck_bonus[]		= {	 0,   1,   2,   3,   4,   5 };

t_level_map_point_2d const t_hero::k_nobility_town_position_none( -1, -1, -1 );  // m_nobility_town_position equals this after read() if the hero is not linked to a town
t_level_map_point_2d const t_hero::k_nobility_town_position_not_in_use( -2, -3, -4 ); // m_nobility_town_position equals this when not between read() and read_postplacement()

// --------------------------------------------------------
// unnamed namespace
// --------------------------------------------------------

namespace
{

	int const k_current_map_format_version = 10;

	// --------------------------------------------------------
	// --------------------------------------------------------
	bool read_old_built_in_event(
		std::streambuf &			stream,
		int							version,
		t_ownable_built_in_event &	event )
	{
		if ( get< t_uint8 >( stream ) == 0 )
			return true;

		t_ownable_built_in_event::t_script_ptr new_script_ptr =
			t_ownable_built_in_event::t_script::reconstruct( stream, version );

		if ( new_script_ptr.get() == 0 )
			return false;

		event.set_script( new_script_ptr );

		return true;
	}

	// -------------------------------------------------------------------
	// -------------------------------------------------------------------
	bool read_old_timed_event(
		std::streambuf &		stream,
		int						version,
		t_ownable_timed_event &	event )
	{
		int first_occurrence = get< t_uint16 >( stream );
		int recurrence_interval = get< t_uint16 >( stream );

		bool execution_flag = false;
		if ( version >= 1 )
		{
			get< bool >( stream ); // Discard "current player only" flag
			execution_flag = get< bool >( stream );
		}

		t_ownable_timed_event::t_script_ptr script_ptr =
			t_ownable_timed_event::t_script::reconstruct( stream, version );
		if ( script_ptr.get() == 0 )
			return false;

		event.set_first_occurrence( first_occurrence );
		event.set_recurrence_interval( recurrence_interval );
		if ( execution_flag )
			event.set_execution_flag();
		else
			event.clear_execution_flag();
		event.set_script( script_ptr );

		return true;
	}

	// -------------------------------------------------------------------
	// -------------------------------------------------------------------
	template < typename t_temp_bonus_list >
	void subtract_temporary_bonuses(
		int ( &						bonuses )[ k_stat_type_count ],
		t_temp_bonus_list const &	temp_bonuses )
	{
		t_temp_bonus_list::const_iterator temp_bonus_end = temp_bonuses.end();
		t_temp_bonus_list::const_iterator temp_bonus_iter = temp_bonuses.begin();
		for ( ; temp_bonus_iter != temp_bonus_end; ++temp_bonus_iter )
		{
			t_stat_type stat_type = temp_bonus_iter->stat;
			assert( stat_type >= 0 && stat_type < k_stat_type_count );
			bonuses[ stat_type ] -= temp_bonus_iter->amount;
		}
	}

} // unnamed namespace

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
void t_hero::initialize()
{
	m_ai_best_spell = k_spell_none;
	m_ai_best_spell_damage = -1;
	m_alignment = k_town_none;
	m_biography_id = -1;
	m_hero_class = k_hero_class_none;
	m_dead = false;
	m_portrait_id = -1;
	m_gender = k_gender_unknown;
	m_last_combat_offer = 0;
	m_level = 1;
	m_experience = 0;
	m_imprisoned = false;
	m_max_experience = false;
	m_nobility_town = 0;
	m_nobility_town_id = k_invalid_global_id;
	m_nobility_town_position = k_nobility_town_position_not_in_use;
	m_nobility_priority = 0;
	m_well_recharge_level = 0;
	m_wounds = 0;
	m_sphinxes_visited = 0;
	m_summoning_points = 0;
	m_named_by_map = false;
	m_use_spellcaster_model = false;
	m_ai_importance = k_ai_importance_normal;

	std::fill( &m_skills[ 0 ], &m_skills[ k_skill_count ], k_mastery_none );

	std::fill(
		&m_built_in_events[ 0 ],
		&m_built_in_events[ k_built_in_event_count ],
		t_built_in_event_ptr( new t_built_in_event ) );
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
t_hero::t_hero()
{
	initialize();
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
t_hero::t_hero( t_default_hero const& data, t_town_type alignment )
{
	initialize();

	m_name = data.name;
	m_alignment = alignment;
	m_biography = data.biography;
	m_biography_id = data.biography_id;
	set_class( data.hero_class );
	m_portrait_id = data.portrait_id;
	m_gender = data.is_male ? k_gender_male : k_gender_female;
	m_use_spellcaster_model = is_spellcaster( get_class() );

	if (get_class() < k_hero_class_basic_count)
	{
		std::vector<t_skill> const& skills = get_starting_skills( get_class() );

		for (int i = 0; i < skills.size(); i++)
			learn_skill( skills[i] );
	}
	m_spell_points = get_maximum_spell_points();

	m_ai_best_spell = k_spell_none;
	m_ai_best_spell_damage = 0;
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
t_hero::t_hero( t_hero_carryover_data const & source )
{
	initialize();

	m_alignment = source.get_alignment();
	m_name = source.get_name();
	m_named_by_map = source.is_named_by_map();
	m_biography = source.get_biography();
	m_biography_id = source.get_biography_id();
	set_class( source.get_class() );
	m_portrait_id = source.get_portrait_id();
	m_gender = source.is_male() ? k_gender_male : k_gender_female;
	m_experience = source.get_experience();
	m_level = get_level( m_experience );
	m_spellbook = source.get_spellbook();
	m_use_spellcaster_model = source.use_spellcaster_model();
	
	t_artifact_slot slot;
	for ( slot = t_artifact_slot( 0 ); slot < k_artifact_slot_hero_count; enum_incr( slot ) )
		m_artifacts[ slot ] = source.get_artifact( slot );

	typedef t_hero_carryover_data::t_backpack t_backpack;
	t_backpack const & backpack = source.get_backpack();
	t_backpack::const_iterator backpack_end = backpack.end();
	t_backpack::const_iterator backpack_iter = backpack.begin();
	for ( ; backpack_iter != backpack_end; ++backpack_iter )
		add_to_backpack( *backpack_iter );

	t_skill_type skill;
	for ( skill = t_skill_type( 0 ); skill < k_skill_type_count; enum_incr( skill ) )
		m_skills[ skill ] = source.get_mastery( skill );

	t_stat_type stat_type;
	for ( stat_type = t_stat_type( 0 ); stat_type < k_stat_type_count; enum_incr( stat_type ) )
	{
		int bonus = source.get_bonus( stat_type );
		if ( bonus != 0 )
			add_bonus( stat_type, bonus );
	}
}

// --------------------------------------------------------
// destroy a hero (object)
// --------------------------------------------------------
t_hero::~t_hero()
{
	// Make sure no pointers are left to this object. This is needed since
	// (to avoid circular references), the nobility list in t_abstract_town
	// has t_hero *'s, not t_counter_ptr<t_hero>'s. 
	clear_nobility_town();
}

void t_hero::set_class( t_hero_class hero_class )
{
	m_hero_class = hero_class;
	if (m_hero_class != k_hero_class_none)
		m_hero_class_artifact = get_class_ability( hero_class );
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
std::string  t_hero::get_name( bool, int ) const
{
	return m_name;
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
t_bitmap_layer_ptr t_hero::get_portrait( int height, bool force_awake_portrait ) const
{
	if( !force_awake_portrait )
	{
		if (m_dead)
			return get_dead_portrait( height );
	}
	return get_hero_portrait( m_portrait_id, height );
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
std::vector<t_skill> t_hero::get_skills() const
{
	std::vector<t_skill> result;
	int                  i;

	for (i = 0; i < k_skill_count; i++)
		if (m_skills[i] > k_mastery_none)
			result.push_back( t_skill( t_skill_type(i), m_skills[i] ) );
	return result;
}

// --------------------------------------------------------
// get the hero sound
// --------------------------------------------------------
t_sound_ptr t_hero::get_sound( t_adv_actor_action_id action )
{
	static t_sound_cache	horse_sound_cache( "hero horse.walk" );

	if ( k_adv_actor_action_walk ) 
	{
		return horse_sound_cache.get();
	}
	// otherwise just try to get the sound for other actions
	return t_creature_stack::get_sound( action );
}

std::vector<t_skill> t_hero::get_primary_skills() const
{
	std::vector<t_skill>    result;
	t_skill_type            skill;

	for (skill = t_skill_type(0); skill < k_skill_primary_count; enum_incr( skill ))
	{
		if (m_skills[skill] >= k_mastery_basic)
			result.push_back( t_skill( t_skill_type(skill), m_skills[skill] ) );
	}
	return result;
}

// --------------------------------------------------------
// Get information about a hero's skills
// --------------------------------------------------------
std::vector<t_skill> t_hero::get_upgradable_skills() const
{
	std::vector<t_skill>    result;
    t_skill_type            primary_skill;
    t_skill                 skill;

	for (skill.skill = k_skill_tactics; skill.skill < k_skill_count; enum_incr( skill.skill ))
    {
		skill.level = t_skill_mastery( m_skills[skill.skill] );
        
        primary_skill = (get_primary_skill (skill.skill));

        
        /*
        ** Only get primary/secondary skills which the hero has at least a basic mastery, 
        ** but less than a grand mastery of.
        */
		if ((skill.level < k_mastery_basic) || (skill.level >= k_mastery_grand_master))
        {
            continue;
        }

        // Inc. skill level.
		enum_incr (skill.level); // = t_skill_mastery( skill.level + 1 );
        // If hero can learn it, add it. 
		if (can_learn( skill ))
        {
		    result.push_back( t_skill( skill ) );
        }
    }
	return result;
}


// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
int t_hero::get_spell_points() const
{
	return m_spell_points;
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
bool t_hero::can_add( t_creature_type creature ) const
{
	return false;
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
bool t_hero::can_add( t_creature_stack const& arg ) const
{
	return false;
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
bool t_hero::can_cast( t_spell spell ) const
{
	// TODO: Handle potions & wands (maybe at t_stack_with_backpack level??)
	return knows_spell(spell) && m_spell_points >= get_spell_cost(spell);
}


// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
t_hero const* t_hero::get_const_hero() const
{
	return this;
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
int	t_hero::get_number() const
{
	return 1;
}


// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
static t_skill_type const k_spell_point_skills[] = 
{ k_skill_healing_magic, k_skill_enchantment, k_skill_black_magic, k_skill_conjuration,
  k_skill_druid_magic };
static t_skill_type const k_power_skills[k_magic_town_type_count] =
{ k_skill_spirit_magic, k_skill_mind_magic, k_skill_demonology, k_skill_destructive_magic, 
  k_skill_meditation };

int t_hero::get_maximum_spell_points() const
{
	int result = 10;
	int i;
	t_skill_mastery level;

	for (i = 0; i < ELEMENTS_OF( k_spell_point_skills ); ++i)
	{
		level = m_skills[k_spell_point_skills[i]];
		if (level == k_mastery_none)
			continue;

		result += 10 * (level + 1);
	}

	result += get_artifact_spell_point_modifier( *this );
	result += m_bonus[k_stat_spell_points];

	if ( result < 0 )
		result = 0;

	return result;
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
int t_hero::get_damage_low( int level ) const
{
	int result = 16;

	result += result * (level - 1) / 10;

	if ( is_spell_active( k_spell_strength ) )
		result += result / 4;
	return result;
}


int t_hero::get_damage_low() const
{
	return get_damage_low( m_level );
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
int t_hero::get_damage_high( int level ) const
{
	int result = 24;

	result += result * (level - 1) / 10;

	if ( is_spell_active( k_spell_strength ) )
		result += (result + 3) / 4;
	return result;
}

int t_hero::get_damage_high() const
{
	return get_damage_high( m_level );
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
int t_hero::get_hit_points( int level ) const
{
	int result = 100;

	result += result * (level - 1) / 10;
	result = result * (100 + get_artifact_health_bonus( *this )) / 100;
	if ( is_spell_active( k_spell_health_boost ) )
		result += ( result + 2 ) >> 2;
	return result;
}

int t_hero::get_hit_points() const
{
	return get_hit_points( m_level );
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
int t_hero::get_combat_movement() const
{
	return 2400;
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
int t_hero::get_speed() const
{
	int result = 6 + m_bonus[k_stat_speed] + get_artifact_speed_modifier( *this );

	if (is_spell_active( k_spell_speed ))
		result += 3;

	if ( result < 1 )
		result = 1;
	
	return result;
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
int t_hero::compute_scouting_range() const
{
	if (is_dead())
		return 0;
    return get_skill( k_skill_scouting ) + 10 + get_artifact_scouting_bonus( *this );
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
int t_hero::get_shots() const
{
	if (m_skills[k_skill_archery] == k_mastery_none
		&& !artifact_gives_ability( *this, k_ability_ranged ))
		return 0;
	if (m_skills[k_skill_archery] < k_mastery_grand_master)
		return 12;
	return 24;
}

// --------------------------------------------------------
// information about a hero
// add a new artifact to the hero
// --------------------------------------------------------
bool t_hero::equip( t_artifact const& artifact, t_artifact_slot slot )
{
	if (m_artifacts[slot].get_icon() != k_artifact_none)
		return false;
	if (slot == k_artifact_slot_shield)
	{
		t_artifact_type weapon = m_artifacts[k_artifact_slot_weapon].get_icon();

		if (weapon != k_artifact_none && get_slot( weapon ) == k_artifact_slot_2_handed)
			return false;
	}
	m_artifacts[slot] = artifact;
	update_artifact_spells();
	return true;
}

// --------------------------------------------------------
// information about a hero
// add a new artifact to the hero
// --------------------------------------------------------
bool t_hero::equip( t_artifact const& artifact )
{
	t_artifact_slot slot = get_slot( artifact.get_icon() );

	if (slot < k_artifact_slot_hero_count)
		return equip( artifact, slot );
	switch (slot)
	{
		case k_artifact_slot_2_handed:
		{
			if (m_artifacts[k_artifact_slot_shield].get_icon() != k_artifact_none)
				return false;
			return equip( artifact, k_artifact_slot_weapon );
		}
		case k_artifact_slot_misc:
		{
			for (slot = k_artifact_slot_misc_1; slot <= k_artifact_slot_misc_4; enum_incr(slot))
			{
				if (equip( artifact, slot ))
					return true;
			}
			return false;
		}
		case k_artifact_slot_ring:
		{
			if (equip( artifact, k_artifact_slot_right_ring ))
				return true;
			
			return equip( artifact, k_artifact_slot_left_ring );
		}
	}

	return false;
}

// --------------------------------------------------------
// information about a hero
// add a new artifact to the hero
// --------------------------------------------------------
bool t_hero::add( t_artifact const& artifact, bool equip_it )
{
	if (equip_it && equip( artifact ))
		return true;
	return t_stack_with_backpack::add( artifact, equip_it );
}

// --------------------------------------------------------
// check if it's legal for a hero to learn a skill
// --------------------------------------------------------
bool t_hero::can_learn( t_skill const& skill ) const
{
	std::vector<t_skill> const&			 requirements = get_requirements( skill );
	std::vector<t_skill>::const_iterator index;

	for (index = requirements.begin(); index != requirements.end(); index++)
	{
		if (m_skills[index->skill] < index->level)
			return false;
	}
	return true;
}

// --------------------------------------------------------
// choose a skill based on weights from a list
// --------------------------------------------------------
t_skill t_hero::choose_skill( std::vector<t_skill> const& candidates ) const
{
	int									 weight;
	int									 total = 0;
	std::vector<t_skill>::const_iterator skill;

	for (skill = candidates.begin(); skill != candidates.end(); skill++)
	{
		weight = get_skill_weight( get_class(), skill->skill );
		if (m_skills[skill->skill] > k_mastery_none)
			if (weight < 2)
				weight = 2;
		total += weight;
	}

	if (total == 0)
		return t_skill();
	total = random( 0, total - 1 );

	for (skill = candidates.begin(); skill != candidates.end(); skill++)
	{
		weight = get_skill_weight( get_class(), skill->skill );
		if (m_skills[skill->skill] > k_mastery_none)
			if (weight < 2)
				weight = 2;
		total -= weight;
		if (total < 0)
			return *skill;
	}
	return t_skill();
}

int t_hero::get_primary_skill_count() const
{
	int          primary_count = 0;
	t_skill_type skill;

	for (skill = t_skill_type(0); skill < k_skill_primary_count; enum_incr( skill ))
	{
		if (m_skills[skill] >= k_mastery_basic)
			primary_count++;
	}
	return primary_count;
}


// --------------------------------------------------------
// information about a hero
// get choices for skills on increasing level
// --------------------------------------------------------
static bool increase_existing_skills( t_hero const& hero, std::vector<t_skill>& result, 
									  t_adventure_map const & map, 
									  bool* used ) 
{
	std::bitset<k_skill_primary_count>	unused_families;
	std::bitset<k_skill_primary_count>	legal_families;
	std::vector<t_skill>				candidates;
	int									i;
	t_skill								skill;
	t_skill_type						primary_skill;

	unused_families.set();
	legal_families.reset();

	for (i = 0; i < result.size(); ++i)
		unused_families[ get_primary_skill( result[i].skill ) ] = false;
	for (skill.skill = t_skill_type(0); skill.skill < k_skill_count; enum_incr( skill.skill ))
	{
		if (used[skill.skill])
			continue;
		// skip skill if we don't know the primary skill
		primary_skill = get_primary_skill( skill.skill );
		if (hero.get_skill( primary_skill ) == k_mastery_none)
			continue;
		skill.level = t_skill_mastery( hero.get_skill( skill.skill ) + 1 );
		if (skill.level >= k_mastery_count || !hero.can_learn( skill ))
			continue;
		// Don't learn if map does not allow this skill
		if ( skill.level == k_mastery_basic && !map.get_allowed_skills()[skill.skill] )
			continue;
		candidates.push_back( skill );
		legal_families[primary_skill] = true;
	}
	if (candidates.empty())
		return false;

	// find primary skills for this class
	if (result.size() < 2)
	{
		std::vector<t_skill> const&			class_skills = get_starting_skills( hero.get_class() );
		std::bitset<k_skill_primary_count>	class_families;
		
		class_families.reset();
		for (i = 0; i < class_skills.size(); ++i)
		{
			if (class_skills[i].skill >= k_skill_primary_count)
				continue;
			class_families[class_skills[i].skill] = true;
		}

		// try and restrict to class skills
		class_families &= legal_families;
		if (class_families.count() > 0)
			legal_families = class_families;
	}

	// try and restrict to unused skills
	unused_families &= legal_families;
	if (unused_families.count() > 0)
		legal_families = unused_families;

	int choice = random( legal_families.count() );

	for (skill.skill = t_skill_type(0); skill.skill < k_skill_primary_count; 
		 enum_incr(skill.skill))
	{
		if (!legal_families[skill.skill])
			continue;
		if (choice-- == 0)
			break;
	}
	i = candidates.size();
	while (i--)
	{
		primary_skill = get_primary_skill(candidates[i].skill);
		if (!legal_families[primary_skill])
			candidates.erase( candidates.begin() + i );
	}
	assert( !candidates.empty() );
	skill = candidates[ random( candidates.size() ) ];
	used[skill.skill] = true;
	result.push_back( skill );
	return true;
}

// --------------------------------------------------------
// add a new primary skill
// --------------------------------------------------------
void t_hero::add_primary_skill( std::vector<t_skill>& result, 
							    t_adventure_map const & map, bool* used ) const
{
	std::vector<t_skill> candidates;
	t_skill              skill;

	for (skill.skill = t_skill_type(0); skill.skill < k_skill_primary_count; enum_incr( skill.skill ))
	{
		if (used[skill.skill])
			continue;
		skill.level = t_skill_mastery( get_skill(skill.skill) + 1 );
		if ( skill.level == k_mastery_basic && !map.get_allowed_skills()[skill.skill] )
			continue;
		if (skill.level == k_mastery_basic && can_learn( skill ))
			candidates.push_back( skill );
	}
	skill = choose_skill( candidates );
	if (skill.skill != k_skill_none)
	{
		used[skill.skill] = true;
		result.push_back( skill );
	}
}


// --------------------------------------------------------
// information about a hero
// get choices for skills on increasing level
// --------------------------------------------------------
std::vector<t_skill> t_hero::get_skill_choices( t_adventure_map const & map ) 
{
	// find an existing skill that is can be upgraded
	std::vector<t_skill> result;
	std::vector<t_skill> candidates;
	t_skill              skill;
	bool                 used[k_skill_count];
	int					 primary_count = 0;
	bool				 primary_added = false;
	t_skill_set const &  allowed_skills = map.get_allowed_skills();

	// count primary skills
	memset( used, 0, sizeof( used ));
	primary_count = get_primary_skill_count();
	increase_existing_skills( *this, result, map, used );
	increase_existing_skills( *this, result, map, used );
	if (primary_count == k_primary_skill_limit)
		increase_existing_skills( *this, result, map, used );
		
	// learn Combat once every 3 levels 
	if (result.size() < 3										
		&& !used[k_skill_combat_defense]
		&& m_skills[k_skill_combat_defense] < k_mastery_grand_master
		&& (primary_count < k_primary_skill_limit || get_skill( k_skill_combat_defense ) > k_mastery_none)
		&& m_level >= m_last_combat_offer + 3 
		&& allowed_skills[k_skill_combat_defense])
	{
		skill.skill = k_skill_combat_defense;
		skill.level = t_skill_mastery( get_skill( k_skill_combat_defense ) + 1 );
		used[skill.skill] = true;
		result.push_back( skill );
		if (skill.skill == k_mastery_basic)
			primary_added = true;
	}

	if (result.size() < 3)
	{
		int		skill_count = 0;

		// if we have less than 6 skills per primary skill, force the hero
		// to increase an existing skill.
		for (skill.skill = t_skill_type(0); skill.skill < k_skill_count; enum_incr( skill.skill ))
			skill_count += m_skills[skill.skill] + 1;
		if (skill_count / 6 < primary_count)
			increase_existing_skills( *this, result, map, used );
		else if (primary_count < k_primary_skill_limit)
		{
			add_primary_skill( result, map, used );
			primary_added = true;
		}
	}
	if (result.size() >= 2 && !primary_added)
		return result;

	// fill out the choices to 3 with any skill at all, but use weights.
	while (result.size() < 3)
	{
		candidates.clear();
		for (skill.skill = t_skill_type(0); skill.skill < k_skill_count; enum_incr( skill.skill ))
		{
			if (used[skill.skill])
				continue;
			skill.level = t_skill_mastery( m_skills[skill.skill] + 1 );
			if ( skill.level == k_mastery_basic && !map.get_allowed_skills()[skill.skill] )
				continue;
			if (primary_count >= k_primary_skill_limit && skill.skill < k_skill_primary_count
				&& skill.level == k_mastery_basic)
				continue;
			if (skill.level < k_mastery_count && can_learn( skill ))
				candidates.push_back( skill );
		}
		skill = choose_skill( candidates );
		if (skill.skill == k_skill_none)
			break;
		used[skill.skill] = true;
		result.push_back( skill );
	}

	// fill out choices to 3, and disregard weights.
	while (result.size() < 3)
	{
		candidates.clear();
		for (skill.skill = t_skill_type(0); skill.skill < k_skill_count; enum_incr( skill.skill ))
		{
			if (used[skill.skill])
				continue;
			skill.level = t_skill_mastery( m_skills[skill.skill] + 1 );
			if ( skill.level == k_mastery_basic && !map.get_allowed_skills()[skill.skill] )
				continue;
			if (primary_count >= k_primary_skill_limit && skill.skill < k_skill_primary_count
				&& skill.level == k_mastery_basic)
				continue;
			if (skill.level < k_mastery_count && can_learn( skill ))
				candidates.push_back( skill );
		}
		if (candidates.size() == 0)
			break;
		skill = candidates[ random( candidates.size() ) ];
		used[skill.skill] = true;
		result.push_back( skill );
	}

	// record offering of combat
	int i;

	for (i = 0; i < result.size(); ++i)
		if (result[i].skill == k_skill_combat_defense)
			m_last_combat_offer = m_level;
	return result;
}

// --------------------------------------------------------
// information about a hero
// get class name string
// --------------------------------------------------------
std::string t_hero::get_class_name() const
{
	return ::get_class_name( get_class() );
}

// --------------------------------------------------------
// information about a hero
// get class name string
// --------------------------------------------------------
static std::vector<int> g_experience;
int const k_hero_level_limit = 70;

int t_hero::get_experience( int level )
{
		
	if (level < 2)
		return 0;
	if (level > k_hero_level_limit)
		level = k_hero_level_limit;
	level--;
	if (g_experience.size() < 3)
	{   // first 3 experience levels are 0, 1000, 2000
		g_experience.push_back( 0 );
		g_experience.push_back( 1000 );
		g_experience.push_back( 2000 );
	}
	while (level >= g_experience.size())
	{
		// later experience levels are (last difference * 120%) rounded to down to nearest 100
		int size  = g_experience.size();
		int last  = g_experience[size - 1];
		int delta = (last - g_experience[size - 2]) * 0.012;

		delta *= 100;
		g_experience.push_back( last + delta );
	}
	return g_experience[level];
}

// --------------------------------------------------------
// information about a hero
// get class name string
// --------------------------------------------------------
int t_hero::get_level( int experience )
{
	int high = g_experience.size();
	int low  = 0;

	// insure that our experience table includes this value.
	if (high == 0)
		high = 10;
	while (get_experience( high ) < experience && g_experience.size() < k_hero_level_limit - 1 )
		high *= 2;

	// perform a binary search on the table to find the correct level.
	std::vector<int>::const_iterator result;

	result = std::lower_bound( g_experience.begin(), g_experience.end(), experience );
	if (*result == experience)
		result++;
	return result - g_experience.begin();
}

// --------------------------------------------------------
// information about a hero
// update hero class if it should change
// --------------------------------------------------------
std::string t_hero::get_class_description() const
{
	std::string result = ::get_class_description( get_class() );

	return replace_keywords( result, this );
}

// --------------------------------------------------------
// information about a hero
// check if hero has changed class
// --------------------------------------------------------
static t_external_string const k_text_new_class_text( "hero_new_class_text.misc" );
bool t_hero::update_class( bool show_dialog )
{
	std::vector<t_skill> skills = get_skills();
	t_hero_class		 best_class = get_best_class( skills );

	if (best_class == get_class())
		return false;

	int current_weight = get_class_weight( get_class(), skills );
	int best_weight    = get_class_weight( best_class, skills );

	if (best_weight <= current_weight )
		return false;
	if (best_class < k_hero_class_basic_count)
		return false;
	set_class( best_class );

	if ( !show_dialog )
		return true;

	std::string text;
	t_basic_dialog* dialog;
	t_window*       window;
	t_screen_rect   rect(0,0,280,280);

	dialog = new t_basic_dialog;
	text = k_text_new_class_text;
	text = replace_keywords( text, "%hero_name", get_name(), 
								   "%level", format_string( "%i", m_level ), 
								   "%hero_class", get_class_name() );
	text += "\n\n";
	text += get_class_description();
	dialog->set_text( text );
	window = new t_combat_model_window( rect, dialog, m_alignment, is_male(), 
		                                m_skills[k_skill_archery] > k_mastery_none,
										m_use_spellcaster_model );
	dialog->add_display_window( window, "" );
	dialog->add_ok_button();
	dialog->open();

	t_sound_cache music = get_music_playing();
	stop_music();
	get_dialog_sound( k_dialog_sound_new_class )->play( get_sound_volume() );
	dialog->run_modal();
	play_music( music );

	return true;
}

// --------------------------------------------------------
// make a decision on which skill to choose
// --------------------------------------------------------
static t_skill ai_choose_skill( t_hero const& hero, std::vector< t_skill > const& skills )
{
	int desired_combat_level = hero.get_level() / 3 - 1;
	int i;

	// first, get a minimum level of combat defense.
	for (i = 0; i < skills.size(); ++i)
	{
		if (skills[i].skill == k_skill_combat_defense
			&& skills[i].level <= desired_combat_level)
			return skills[i];
	}
	// next, get a minimum level of magic resistance.
	int desired_resistance_level = hero.get_level() / 6 - 1;

	for (i = 0; i < skills.size(); ++i)
	{
		if (skills[i].skill == k_skill_magic_resistance
			&& skills[i].level <= desired_resistance_level)
			return skills[i];
	}

	return skills[random( skills.size() ) ];	
}


extern t_external_string const k_max_level_reached("hero_max_level_reached.misc");
extern t_external_string const k_max_level_reached_title("hero_max_level_reached_title.misc");

// --------------------------------------------------------
// information about a hero
// check hero's level
// --------------------------------------------------------
void t_hero::check_level( t_adventure_map const & map, t_window* parent, t_player *owner )
{
	int final_level = get_level( m_experience );
	int hits;

	if ( owner && owner->has_max_level() && final_level > owner->get_max_level() )
	{
		final_level = owner->get_max_level();
	}		

	bool	show_dialog = (owner != 0 && !owner->is_computer() && parent != 0 && !m_max_experience);

	while (final_level > m_level)
	{
		t_skill	new_skill;

		if ( !show_dialog )
		{
			std::vector< t_skill > skill_choices = get_skill_choices( map );

			if ( skill_choices.empty() )
			{
				new_skill.skill = k_skill_none;
			}
			else
			{
				// Placeholder until AI is ready to be smart
				new_skill = ai_choose_skill( *this, skill_choices );
			}
		}
		else
		{
			t_counted_ptr<t_skill_choice_dialog> dialog;

			dialog = new t_skill_choice_dialog( *this, parent, map );
			dialog->run_modal();
			new_skill = dialog->get_choice();
		}

		hits = (get_hit_points() - get_wounds()) * 100 / get_hit_points();
		m_level++;
		if (new_skill.skill != k_skill_none)
		{
			learn_skill( new_skill, show_dialog );
		}
		set_wounds( get_hit_points() - get_hit_points() * hits / 100  );
	}

	if ( owner && owner->has_max_level() 
		 && m_experience > get_experience( owner->get_max_level() ) )
	{
		m_experience = get_experience( owner->get_max_level() );
	
		if (!owner->is_computer() && show_dialog)
		{
			m_max_experience = true;

			t_counted_ptr<t_basic_dialog> dialog = new t_basic_dialog( parent );

			dialog->add_experience_icon();

			std::string text = replace_keywords( k_max_level_reached, this );
			dialog->set_text( text );
			dialog->add_ok_button();
			dialog->set_title( k_max_level_reached_title );
			dialog->open( );
			dialog->run_modal();
		}
	}
}

bool t_hero::can_add_experience( t_player* owner ) const
{
	if ( owner && owner->has_max_level() && m_experience >= get_experience( owner->get_max_level() ) )
	{
        return false;
    }
	if ( m_experience >= get_experience( k_hero_level_limit ) )
        return false;

    return true;
}

int	t_hero::get_experience_needed_for_next_level() const
{
    return ( get_experience(m_level+1) - m_experience );
}

// --------------------------------------------------------
// information about a hero
// has this hero visited a given single use object?
// --------------------------------------------------------
bool t_hero::has_visited( t_single_use_object const& object ) const
{
	return has_visited(object.get_single_use_id());
}

// --------------------------------------------------------
// information about a hero
// has this hero visited a given single use object?
// --------------------------------------------------------
bool t_hero::has_visited(int single_use_id) const
{
	if (single_use_id >= m_visited_objects.size())
		return false;
  return (m_visited_objects[single_use_id] != 0 ? true : false);
}

// --------------------------------------------------------
// information about a hero
// record a visited to a single use object
// --------------------------------------------------------
void t_hero::set_visited( t_single_use_object const& object, bool visited )
{
	set_visited(object.get_single_use_id(), visited);
}

// --------------------------------------------------------
// information about a hero
// record a visited to a single use object
// --------------------------------------------------------
void t_hero::set_visited( int single_use_id, bool visited )
{
	if (single_use_id >= m_visited_objects.size())
		m_visited_objects.insert( m_visited_objects.end(), single_use_id - m_visited_objects.size() + 1, 0 );
  m_visited_objects[single_use_id] = (visited == false ? 0 : 1);
}

// --------------------------------------------------------
// information about a hero
// get "he" or "she"
// --------------------------------------------------------
static t_external_string k_text_he( "he.hero" );
static t_external_string k_text_she( "she.hero" );

std::string t_hero::get_pronoun() const
{
	if ( is_male() )
		return k_text_he;
	return k_text_she;
}

// --------------------------------------------------------
// information about a hero
// get "his" or "her"
// --------------------------------------------------------
static t_external_string k_text_his( "his.hero" );
static t_external_string k_text_her( "her.hero" );

std::string t_hero::get_possessive_pronoun() const
{
	if ( is_male() )
		return k_text_his;
	return k_text_her;
}

// --------------------------------------------------------
// information about a hero
// get "him" or "her"
// --------------------------------------------------------
static t_external_string k_text_him( "him.hero" );
static t_external_string k_text_her_accusative( "her.accusative.hero" );

std::string t_hero::get_accusative_pronoun() const
{
	if ( is_male() )
		return k_text_him;
	return k_text_her;
}


// --------------------------------------------------------
// information about a hero
// get "himself" or "herself"
// --------------------------------------------------------
static t_external_string k_text_himself( "himself.hero" );
static t_external_string k_text_herself( "herself.hero" );

std::string t_hero::get_reflexive_pronoun() const
{
	if ( is_male() )
		return k_text_himself;
	return k_text_herself;
}

// Hero's daily income in fixed point value (8 bits of precision).
void t_hero::get_income( int day, t_material_array& income ) const
{
	if ( is_dead() )
		return; 

    // Get income from artifacts.
    t_material_array artifact_income;
    get_artifact_income( *this, artifact_income );
	income += artifact_income;

	// add bonuses for estates, mining
	t_skill_mastery mastery;
	t_material		material;

    // Listed gold values not fractional.
	static int const k_estates_bonus[k_mastery_count + 1] =
	{ 0, 100, 200, 300, 400, 500 };
	static int const k_material_bonus[k_material_count][k_mastery_count + 1] =
	{
		{ 0, 0, 0, 0, 0,   0 }, // gold
		{ 0, 8, 8, 8, 12, 16 }, // wood
		{ 0, 8, 8, 8, 12, 16 }, // ore
		{ 0, 0, 2, 4,  5,  6 }, // crystal
		{ 0, 0, 2, 4,  5,  6 }, // sulfur
		{ 0, 0, 2, 4,  5,  6 }, // mercury
		{ 0, 0, 2, 4,  5,  6 }, // gems
	};

	mastery = m_skills[k_skill_estates];
	income[ k_gold ] += k_estates_bonus[mastery + 1] * (9 + m_level) / 10;

	// mining uses a convoluted formula intended to distribute income
	// into chunks, where the player sees perceptable changes as often as possible
	mastery = m_skills[k_skill_mining];
	if (mastery > k_mastery_none)
	{
		int	monthly_income;
		int cycle_time;
		int daily_income;
		int day_offset;

		for (material = k_wood; material < k_material_count; enum_incr( material ))
		{
			monthly_income = k_material_bonus[material][mastery + 1] * (9 + m_level);
			// compute integer daily income
			income[material] += monthly_income / 200;
			// compute fractional daily income, in units of 200.
			daily_income = monthly_income % 200;
			if (daily_income == 0)
				continue;

			// how many days does it take to accumulate one unit?
			cycle_time = 200 / daily_income;
			// shift distribution so that wood, ore are distributed in rotation,
			// and crystal, sulfur, mercury, and gems are distibuted in rotation
			if (material <= k_ore)
				day_offset = day + cycle_time * (material - k_wood) / 2;
			else
				day_offset = day + cycle_time * (material - k_crystal) / 4;
			// if this is the delivery day, increment income by one.
			if (day_offset * daily_income / 200 > (day_offset - 1) * daily_income / 200)
				income[material]++;
		}
	}
}

// these arrays are used because some level 3 creatures are excluded at level 3,
// since elementals are level "2.5" creatures.
static t_creature_type const k_1st_level_summoned_creatures[] = 
{ k_leprechaun, k_sprite, k_wolf };
static t_creature_type const k_2nd_level_summoned_creatures[] =
{ k_elf, k_satyr, k_white_tiger };
static t_creature_type const k_3rd_level_summoned_creatures[] =
{ k_air_elemental, k_earth_elemental, k_fire_elemental, k_water_elemental };

static t_creature_type const* k_summoned_creature_arrays[3] =
{
	k_1st_level_summoned_creatures,
	k_2nd_level_summoned_creatures,
	k_3rd_level_summoned_creatures
};

static int k_summoned_creature_array_sizes[3] =
{
	ELEMENTS_OF( k_1st_level_summoned_creatures ),
	ELEMENTS_OF( k_2nd_level_summoned_creatures ),
	ELEMENTS_OF( k_3rd_level_summoned_creatures ),
};
	
// -----------------------------------------------------------------------
// handle post daily summoning
// -----------------------------------------------------------------------
static t_creature_type choose_summoned_creature( t_creature_array& army, int level )
{
	t_creature_type	const*		 creature = k_summoned_creature_arrays[level];
	std::vector<t_creature_type> choices;
	int							 count = k_summoned_creature_array_sizes[level];
	int							 i;

	for (i = 0; i < count; i++, creature++)
	{
		if (!army.can_add( *creature ))
			continue;
		choices.push_back( *creature );
	}
	if (choices.size() == 0)
		return k_creature_none;
	return choices[ random( choices.size() ) ];
}

// -----------------------------------------------------------------------
// handle daily summoning
// -----------------------------------------------------------------------
void t_hero::do_summoning( t_creature_array& army )
{
	// choose something to summon
	t_creature_type creature_type;
	int				total = get_skill_power( k_skill_recruitment );
	int				level;

	if (total <= 0)
		return;
	m_summoning_points += total;
	level = get_skill( k_skill_recruitment ) / 2;
	
	do
	{
		creature_type = choose_summoned_creature( army, level );
		level--;
	} while (level >= 0 && creature_type == k_creature_none);

	if (creature_type == k_creature_none )
		return;

	int amount;
	int	experience = get_traits( creature_type ).experience;

	amount = m_summoning_points / experience;
	if (amount == 0)
		return;

	army.add( creature_type, amount );
	m_summoning_points -= amount * experience;
}

// --------------------------------------------------------
// information about a hero
// recharge hero's spell points on new day.
// --------------------------------------------------------
void t_hero::process_new_day( t_player* player, bool grow, t_creature_array* army, 
							  int day )
{
	t_stack_with_backpack::process_new_day( player, grow, army, day );
	int             i;

	if (m_nobility_town != 0)
	{
		assert(m_nobility_town->is_hero_in_nobility_list(this));
	}

	if (m_dead)
		return;

	// heal
	int hit_points = get_hit_points();

	m_wounds = 0;
	if (player != 0)
	{
		t_material_array income;

		income.set( 0 );
		get_income( day, income );
		player->gain( income );
	}
	if (army != 0)
		do_summoning( *army );

	// compute max mana, and recharge rate.
	int max_mana = get_maximum_spell_points();

	m_well_recharge_level = 0;
	if (m_spell_points >= max_mana)
		return;

	t_skill_mastery level;

	m_spell_points += 2;
	for (i = 0; i < k_magic_town_type_count; ++i)
	{
		level = m_skills[k_spell_point_skills[i]];
		if (level == k_mastery_none)
			continue;
		m_spell_points += 2 * (level + 1);
	}
	m_spell_points += get_artifact_spell_point_recovery( *this );
	if (m_spell_points > max_mana)
		m_spell_points = max_mana;
}

// --------------------------------------------------------
// information about a hero
// learn new spells
// --------------------------------------------------------
void t_hero::learn_spells( t_town const* town )
{
	t_guild_spell_list const& spells = town->get_spells();
	int                       i;

	for (i = 0; i < spells.size(); i++)
	{
		if (!spells[i].available)
			continue;
		learn_spell( spells[i].spell );
	}
}

// --------------------------------------------------------
// Power for this hero's spells
// --------------------------------------------------------
int t_hero::get_spell_power( t_spell spell, t_abstract_grail_data_source const & grail_data, int number ) const
{
	t_town_type alignment = get_spell_alignment( spell );

	int bonus = 0;

	bonus += 20 * (m_skills[k_power_skills[alignment]] + 1);

	// apply sorcery bonus
	if (is_damage( spell ))
	{
		static int const k_sorcery_bonus[] = { 0, 20, 40, 60, 80, 100 };

		bonus += k_sorcery_bonus[m_skills[k_skill_intelligence] + 1];
	}
	bonus += get_artifact_spell_power_modifier( *this, spell );

	bonus += grail_data.get_grail_spell_bonus( alignment );

	return ::get_basic_spell_power( spell, m_level, bonus + 100 );
}

// --------------------------------------------------------
// check if hero knows spell, or if hero has an artifact
// which allows him to cast the spell normally (i.e. book or scroll)
// but NOT if hero has a wand or potion (which is handled elsewhere)
// --------------------------------------------------------
bool t_hero::knows_spell( t_spell spell ) const
{
	if (m_scroll_spells[spell])
		return true;
	if (!m_spellbook[spell] && !m_book_spells[spell])
		return false;
	// check for skill level
	int                       level;
	t_town_type               alignment;

	alignment = get_spell_alignment( spell );
	level = get_spell_level( spell );
	if (m_skills[alignment + k_skill_life_magic] + 1 < level)
		return false;
	return true;
}

// --------------------------------------------------------
// teach the hero the indicated spell, check his skill first return false if no skill
// --------------------------------------------------------
bool t_hero::can_learn( t_spell spell ) const
{
	if (m_spellbook[spell])
		return false;

	t_town_type alignment = get_spell_alignment( spell );
	int			level = get_spell_level( spell );

	return (m_skills[alignment + k_skill_life_magic] + 1 >= level);
}

// --------------------------------------------------------
// teach the hero the indicated spell, check his skill first return false if no skill
// --------------------------------------------------------
bool t_hero::learn_spell (t_spell spell)
{
	if (!can_learn( spell ))
		return false;
	m_spellbook.set(spell);
	return true;
}

// --------------------------------------------------------
// learn all spells cheat
// --------------------------------------------------------
void t_hero::learn_all_spells_cheat()
{
	t_spell spell;

	for (spell = t_spell(0); spell < k_spell_count; enum_incr(spell))
	{
		if (is_teachable( spell ))
			m_spellbook.set( spell );
	}
}

// --------------------------------------------------------
// information about a hero
// get the adjusted cost of casting a spell
// --------------------------------------------------------
int t_hero::get_spell_cost( t_spell spell ) const
{
	return get_artifact_spell_cost( *this, spell );
}


// --------------------------------------------------------
// information about a hero
// get the appropriate combat model
// --------------------------------------------------------
t_combat_actor_model_ptr t_hero::get_combat_model( double scale ) const
{
	bool has_archery = get_skill( k_skill_archery ) > k_mastery_none
	  || artifact_gives_ability( *this, k_ability_ranged );

	return get_combat_actor_model( m_alignment, is_male(), has_archery,
								   m_use_spellcaster_model, scale );
}

// --------------------------------------------------------
// information about a hero
// get the correct missile type for combat
// --------------------------------------------------------
t_missile_type t_hero::get_missile_type() const
{
	if (m_use_spellcaster_model)
		return k_missile_magic;
	return k_missile_arrow;
}

// --------------------------------------------------------
// information about a hero
// write to file
// --------------------------------------------------------
bool t_hero::write( std::streambuf& stream ) const
{
	int i;

	write_header( stream );
	put<t_town_type>( stream, m_alignment );
	for (i = 0; i < k_artifact_slot_hero_count; i++)
		m_artifacts[i].write( stream );
	write_string16( stream, m_biography );
	put< t_int16 >( stream, m_biography_id );
	put< t_hero_class >( stream,  get_class() );
	put< bool >( stream, m_use_spellcaster_model );
	put< t_uint32 >( stream, m_experience );
	put< bool >( stream, is_male() );
	put< t_uint16 >( stream, m_level );
	write_string16( stream, m_name );
	put< t_uint16 >( stream, m_portrait_id );
	::write( stream, m_skills, sizeof( m_skills ));
	put<t_uint16>( stream, m_university_diplomas.size() );

	t_university_diploma_array::const_iterator diploma = m_university_diplomas.begin();

	for (; diploma != m_university_diplomas.end(); ++diploma)
	{
		put<t_uint32>( stream, diploma->first );
		::write( stream, &diploma->second, sizeof( diploma->second ));
	}

	::write( stream, m_spellbook );
	put< t_uint16 >( stream, m_spell_points );
	put< t_uint16 >( stream, m_summoning_points );
	put< bool >( stream, m_dead );
	put< t_uint16 >( stream, m_wounds );
	put< t_uint32 >( stream, m_visited_objects.size() );
  if(m_visited_objects.size() > 0)
    ::write( stream, &(*m_visited_objects.begin()), m_visited_objects.size() );
	put< t_uint32 >( stream, m_eluded_army_ids.size() );

	std::set<int>::const_iterator index = m_eluded_army_ids.begin();

	for (; index != m_eluded_army_ids.end(); ++index)
		put<t_uint32>( stream, *index );

	put< t_uint8 >( stream, m_well_recharge_level );
	put< t_uint8 >( stream, m_sphinxes_visited );
	put<t_uint8>( stream, m_imprisoned );
	put< bool >( stream, m_named_by_map );

	
	// Nobility town is recorded by global id
	if ( !m_nobility_town )
	{
		write_adventure_global_id( stream, k_invalid_global_id );
	}
	else
	{
		write_adventure_global_id( stream, m_nobility_town->get_global_id() );
	}

	put< int >( stream, m_nobility_priority );
	put< t_uint16 >( stream, m_last_combat_offer );

	write_events( stream );

	put< t_uint8 >( stream, m_ai_importance );

	put< t_uint8 >( stream, m_max_experience );

	return true;
}

// --------------------------------------------------------
// information about a hero
// read from a file
// --------------------------------------------------------
bool t_hero::read( std::streambuf& stream )
{
	int i;
	int count;
	int version;

	if ( !read_header( stream, version ) )
		return false;

	m_alignment		= ::get<t_town_type>( stream );
	for (i = 0; i < k_artifact_slot_hero_count; i++)
	{
		if ( !m_artifacts[i].read( stream ) )
			return false;
	}

	if ( !read_string16( stream, m_biography ) )
		return false;

	if ( version >= 15 )
		m_biography_id = get< t_int16 >( stream );
	else
		m_biography_id = -1;

	set_class( ::get< t_hero_class >( stream ) );
	if (version >= 16)
		m_use_spellcaster_model = get< bool >( stream );
	else
		m_use_spellcaster_model = is_spellcaster(get_class());
	m_experience	= ::get< t_uint32 >( stream );
	m_gender		= ::get< bool >( stream ) ? k_gender_male : k_gender_female;
	m_level			= ::get< t_uint16 >( stream );
	
	if ( !read_string16( stream, m_name ) )
		return false;
	m_portrait_id	= ::get< t_uint16 >( stream );
	
	::read( stream, m_skills, sizeof( m_skills ));

	if (version >= 17)
	{
		int					 count = ::get<t_uint16>( stream );
		t_university_diploma diploma;

		while (count--)
		{
			int id = get<t_uint32>( stream );

			::read( stream, &diploma, sizeof(diploma));
			m_university_diplomas.insert( std::make_pair( id, diploma ) );
		}
	}

	if (version == 0)
	{
		if ( !read_version( stream, m_spellbook, 0 ) )
			return false;
	}
	else
	{
		::read( stream, m_spellbook );
	}

	m_spell_points	= ::get< t_uint16 >( stream );
	if (version < 21)
		m_summoning_points = 0;
	else
		m_summoning_points = get<t_uint16>( stream );
	if (version < 3)
	{
		m_dead = false;
		m_wounds = 0;
	}
	else
	{
		m_dead          = ::get< bool >( stream );
		m_wounds		= ::get< t_uint16 >( stream );
	}
	count			= ::get< t_uint32 >( stream );
	m_visited_objects.resize( count );
	::read( stream, &(*m_visited_objects.begin()), m_visited_objects.size() );

	if (version >= 19)
	{
		count = ::get< t_uint32 >( stream );

		m_eluded_army_ids.clear();
		while (count--)
		{
			m_eluded_army_ids.insert( get<t_uint32>( stream ) );
		}
	}

	m_well_recharge_level = ::get< t_uint8 >( stream );
	m_sphinxes_visited = ::get< t_uint8 >( stream );
	if (version < 20)
		m_imprisoned = false;
	else
		m_imprisoned = get<t_uint8>( stream ) != 0;

	if (version < 8)
		m_named_by_map = false;
	else
		m_named_by_map = get< bool >( stream );

	// Read in marker for m_nobility_town. Pointer will be
	// set up in read_postplacement
	if (version < 9)
	{
		m_nobility_town_id = k_invalid_global_id;
		m_nobility_town_position = k_nobility_town_position_none;
	}
	else if ( version < 14 )
	{
		m_nobility_town_id = k_invalid_global_id;
		m_nobility_town_position.row = ::get< int >( stream );
		m_nobility_town_position.column = ::get< int >( stream );
		m_nobility_town_position.level = ::get< int >( stream );
	}
	else
	{
		m_nobility_town_id = read_adventure_global_id( stream );
		m_nobility_town_position = k_nobility_town_position_none;
	}

	if ( version < 11 )
	{
		m_nobility_priority = 0;
	}
	else
	{
		m_nobility_priority = get< int >( stream );
	}
	if ( version < 12 )
	{
		m_last_combat_offer = 0;
	}
	else
	{
		m_last_combat_offer = get< t_uint16 >( stream );
	}

	if ( !read_events( stream, version ) )
		return false;

	if ( version < 14 )
	{
		m_ai_importance = k_ai_importance_normal;
	}
	else
	{
		m_ai_importance = t_ai_importance( get< t_uint8 >( stream ) );
	}

	if ( version < 22 )
		m_max_experience = false;
	else
		m_max_experience = get< t_uint8 >( stream ) != 0;

	update_artifact_spells();
	return true;
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
static bool read_skills_from_map( std::streambuf & stream, int version, std::vector<t_skill>& skills )
{
	if ( version < 3 || get< t_uint8 >( stream ) == 0 )
	{
		return true;
	}

	t_skill_type skill_type;
	for (	skill_type = t_skill_type( 0 );
			skill_type < k_skill_type_count;
			enum_incr( skill_type ) )
	{
		t_skill_mastery mastery = t_skill_mastery( get< t_int8 >( stream ) );

		if ( mastery != k_mastery_none && ( mastery < 0 || mastery >= k_skill_mastery_count ) )
			return false;
		if (mastery != k_mastery_none)
			skills.push_back( t_skill( skill_type, mastery ) );
	}
	return true;
}

// --------------------------------------------------------
// information about a hero
// read from a map
// --------------------------------------------------------
bool t_hero::read_from_map( std::streambuf& stream )
{
	int version = get< t_uint16 >( stream );
	if ( version < 8 || version > k_current_map_format_version )
		return false;

	if ( version < 1 )
		get< t_int16 >( stream ); // Read and discard the old biography id

	std::vector<t_skill> skills;

	m_dead = false;
	m_wounds = 0;
	m_experience = 0;

	if (		!read_portrait_id_from_map( stream )
			||	!read_hero_class_from_map( stream )
			||	!read_gender_from_map( stream )
			||	!read_level_from_map( stream, version )
			||	!read_bonuses_from_map( stream, version )
			||	!read_string16( stream, m_biography )
			||	!read_name_from_map( stream, version ) 
			||	!read_skills_from_map( stream, version, skills )
			||	!read_spellbook_from_map( stream, version )
			||	!read_artifacts_from_map( stream, version )
			||	!( version < 1 || t_stack_with_backpack::read_from_map( stream ) )
			||	!read_built_in_events_from_map( stream, version )
			||	!read_timed_events_from_map( stream, version )
			||	!read_triggerable_events_from_map( stream, version )
			||	!read_continuous_events_from_map( stream, version )
			||  !read_ai_importance_from_map( stream, version ) )
		return false;

	int i;

	// learn skills AFTER reading spellbook, so that 
	// default spells can be set after taking into account spells already known.
	std::fill( &m_skills[ 0 ], &m_skills[ k_skill_type_count ], k_mastery_none );

	for (i = 0; i < skills.size(); ++i)
		learn_skill( skills[i] );

	update_artifact_spells();
	return true;
}

bool t_hero::read_name_from_map( std::streambuf& stream, int version ) 
{
	if (read_string16( stream, m_name ) == false ) 
		return false;

	m_named_by_map = !m_name.empty();

	return true;
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
bool t_hero::read_artifacts_from_map( std::streambuf & stream, int version )
{
	std::fill( &m_artifacts[ 0 ], &m_artifacts[ k_artifact_slot_hero_count ], t_artifact() );

	if ( version < 2 )
		return true;

	t_artifact_slot slot;
	for (	slot = t_artifact_slot( 0 );
			slot < k_artifact_slot_hero_count;
			enum_incr( slot ) )
	{
		if (	get< t_uint8 >( stream ) != 0
			&&	!::read_from_map( stream, m_artifacts[ slot ] ) )
			return false;
	}

	return true;
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
bool t_hero::read_bonuses_from_map( std::streambuf & stream, int version )
{
	if ( version >= 5 )
	{
		int attack;
		int defense;
		if ( version >= 10 )
		{
			attack = get< t_uint8 >( stream );
			defense = get< t_uint8 >( stream );
		}
		else
		{
			attack = get< t_uint16 >( stream );
			defense = get< t_uint16 >( stream );
		}
		int speed = get< t_uint8 >( stream );
		int spell_points = get< t_uint8 >( stream );

		add_bonus( k_stat_attack, attack );
		add_bonus( k_stat_defense, defense );
		add_bonus( k_stat_speed, speed );
		add_bonus( k_stat_spell_points, spell_points );
	}

	return true;
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
bool t_hero::read_built_in_events_from_map( std::streambuf & stream, int version )
{
	assert( version >= 8 );
	return read_hero_built_in_events_from_map( stream, m_built_in_events );
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
bool t_hero::read_continuous_events_from_map( std::streambuf & stream, int version )
{
	assert( version >= 8 );
	return read_hero_continuous_events_from_map( stream, m_continuous_events );
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
bool t_hero::read_gender_from_map( std::streambuf & stream )
{
	m_gender = t_gender( get< t_int8 >( stream ) );
	return		m_gender == k_gender_unknown
			||	m_gender == k_gender_female
			||	m_gender == k_gender_male;
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
bool t_hero::read_hero_class_from_map( std::streambuf & stream )
{
	set_class( t_hero_class( get< t_int8 >( stream ) ) );
	return		get_class() == k_hero_class_none
			||	( get_class() >= 0 && get_class() < k_hero_class_count );
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
bool t_hero::read_level_from_map( std::streambuf & stream, int version )
{
	if ( version >= 5 )
		m_level = get< t_int8 >( stream );
	else
		m_level = 1;
	return true;
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
bool t_hero::read_portrait_id_from_map( std::streambuf & stream )
{
	m_portrait_id = get< t_int16 >( stream );
	return m_portrait_id >= -1 && m_portrait_id < get_hero_portrait_count();
}


// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
bool t_hero::read_spellbook_from_map( std::streambuf & stream, int version )
{
	if ( version < 4 )
	{
		m_spellbook.reset();
		return true;
	}

	return ::read( stream, m_spellbook );
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
bool t_hero::read_timed_events_from_map( std::streambuf & stream, int version )
{
	assert( version >= 8 );
	return read_hero_timed_events_from_map( stream, m_timed_events );
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
bool t_hero::read_triggerable_events_from_map( std::streambuf & stream, int version )
{
	assert( version >= 8 );
	return read_hero_triggerable_events_from_map( stream, m_triggerable_events );
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
bool t_hero::read_events( std::streambuf & stream, int version)
{
	int script_version = 0;
	
	if ( version < 4 )
		return true;

	if ( version > 4 )
		script_version = get< t_uint16 >(stream);

	// Read the built in events
	t_built_in_event_id event_id;
	for (	event_id = t_built_in_event_id( 0 );
			event_id < k_built_in_event_count;
			enum_incr( event_id ) )
	{
		t_built_in_event_ptr new_event_ptr( new t_built_in_event );
		bool read_result;
		if ( version < 10 )
			read_result = read_old_built_in_event( stream, script_version, *new_event_ptr );
		else
			read_result = new_event_ptr->read( stream, script_version );
		if ( !read_result )
			return false;

		m_built_in_events[ event_id ] = new_event_ptr;
	}
	
	t_timed_event_list new_timed_events;
	t_triggerable_event_list new_triggerable_events;
	t_continuous_event_list new_continuous_events;
			
	// Read the timed events
	int count = get< t_uint16 >( stream );
	new_timed_events.reserve( count );

	while ( count > 0 )
	{
		t_timed_event_ptr new_event_ptr( new t_timed_event );
		bool read_result;
		if ( version < 10 )
			read_result = read_old_timed_event( stream, script_version, *new_event_ptr );
		else
			read_result = new_event_ptr->read( stream, script_version );

		new_timed_events.push_back( new_event_ptr );

		--count;
	}

	// Read the triggerable and continuous events
	count = get< t_uint16 >( stream );
	if ( version < 10 )
	{
		// Read and convert from the old format
		while ( count > 0 )
		{
			std::string name;
			if ( !read_string16( stream, name ) )
				return false;

			bool continuous = get< bool >( stream );

			bool current_player_only = true;
			if ( version >= 1 )
			{
				current_player_only = get< bool >( stream );
				get< bool >( stream ); // discard "execution" flag
			}

			t_script_event::t_script_ptr new_script_ptr =
				t_script_event::t_script::reconstruct( stream, script_version );
			if ( new_script_ptr.get() == 0 )
				return false;

			if ( continuous )
			{
				t_continuous_event_ptr new_event_ptr( new t_continuous_event );
				new_event_ptr->set_run_only_during_owners_turn( current_player_only );
				new_event_ptr->set_script( new_script_ptr );
				new_continuous_events.push_back( new_event_ptr );
			}
			else
			{
				t_triggerable_event_ptr new_event_ptr( new t_triggerable_event );
				new_event_ptr->set_name( name );
				new_event_ptr->set_script( new_script_ptr );
				new_triggerable_events.push_back( new_event_ptr );
			}

			--count;
		}
	}
	else
	{
		// Read triggerable events
		new_triggerable_events.reserve( count );

		while ( count > 0 )
		{
			t_triggerable_event_ptr new_event_ptr( new t_triggerable_event );
			if ( !new_event_ptr->read( stream, script_version ) )
				return false;

			new_triggerable_events.push_back( new_event_ptr );

			--count;
		}

		// Read continuous events
		count = get< t_uint16 >( stream );
		new_continuous_events.reserve( count );

		while ( count > 0 )
		{
			t_continuous_event_ptr new_event_ptr( new t_continuous_event );
			if ( !new_event_ptr->read( stream, script_version ) )
				return false;

			new_continuous_events.push_back( new_event_ptr );

			--count;
		}
	}

	m_timed_events.swap( new_timed_events );
	m_triggerable_events.swap( new_triggerable_events );
	m_continuous_events.swap( new_continuous_events );
	
	return true;
}


// --------------------------------------------------------
// Read AI importance from map file
// --------------------------------------------------------
bool t_hero::read_ai_importance_from_map( std::streambuf & stream, int version )
{
	if ( version < 9 )
	{
		m_ai_importance = k_ai_importance_normal;
	}
	else
	{
		m_ai_importance = t_ai_importance( get< t_uint8 >( stream ) );
	}

	return true;
}


// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
bool t_hero::write_events( std::streambuf & stream ) const
{
	put< t_uint16 > (stream, t_abstract_script_action::get_version());	

	t_built_in_event_id event_id;
	for (	event_id = t_built_in_event_id( 0 );
			event_id < k_built_in_event_count;
			enum_incr( event_id ) )
	{
		if ( !m_built_in_events[ event_id ]->write( stream ) )
			return false;

	}
	
	int count = m_timed_events.size();
	put< t_uint16 >( stream, count );
	int i;
	for (i = 0; i < count; i++) {
		if (m_timed_events[i]->write( stream ) == false) 
			return false;
	}

	count = m_triggerable_events.size();
	put< t_uint16 >( stream, count );
	for (i = 0; i < count; i++) {
		if (m_triggerable_events[i]->write( stream ) == false) 
			return false;
	}

	count = m_continuous_events.size();
	put< t_uint16 >( stream, count );
	for (i = 0; i < count; i++) {
		if (m_continuous_events[i]->write( stream ) == false) 
			return false;
	}

	return true;
}

// --------------------------------------------------------
// information about a hero
// get experience value of hero
// --------------------------------------------------------
int t_hero::get_experience_value( int number ) const
{
	return 100 + m_level * 100;
}

// --------------------------------------------------------
// information about a hero
// get ai value of hero
// --------------------------------------------------------
float t_hero::ai_value() const
{
	if (is_dead())
		return 0.0f;
	
	return ai_value_including_dead();
}

// --------------------------------------------------------
// information about a hero
// get ai value of hero
// --------------------------------------------------------
float t_hero::ai_value( t_creature_array const* army ) const
{
	float bonus_modifier = 1.0f + ai_bonus_for_morale( 
									m_bonus[k_stat_morale] 
									+ army->get_ai()->get_cache().get_alignment_modifier( army, get_alignment()) );

	return ai_value() * bonus_modifier;
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
float t_hero::ai_value_including_dead() const
{
	return sqrt( (float)(ai_get_total_offense() * ai_get_total_defense()) )
				 * k_ai_creature_value_modifier;
}

// -----------------------------------------------------------
// -----------------------------------------------------------
int t_hero::ai_get_total_defense() const
{
	return
		(t_stack_with_backpack::ai_get_total_defense(false) 
		+ t_stack_with_backpack::ai_get_total_defense(true)) / 2;
}

// -----------------------------------------------------------
// -----------------------------------------------------------
int t_hero::ai_get_total_offense( t_offense_type type ) const
{
	switch (type)
	{
		case k_offense_spell:
			return m_ai_best_spell_damage * 2;
		case k_offense_ranged:
			return t_stack_with_backpack::ai_get_total_offense(true);
		case k_offense_melee:
			return t_stack_with_backpack::ai_get_total_offense(false);
		default:
			assert(false);
			return 0;
	}
}

// -----------------------------------------------------------
// -----------------------------------------------------------
int	t_hero::ai_get_total_offense() const
{
	float best_offense;
	int ranged_offense = ai_get_total_offense( k_offense_ranged );
	int melee_offense = ai_get_total_offense( k_offense_melee );

	if (ranged_offense > melee_offense) 
		best_offense = ranged_offense;
	else 
		best_offense = melee_offense;

	int spell_offense = ai_get_total_offense( k_offense_spell );

	if (best_offense < spell_offense)
		best_offense = spell_offense;

	return best_offense;
}

// -----------------------------------------------------------
// -----------------------------------------------------------
void t_hero::ai_start_turn( t_creature_array const* army )
{
	m_ai_best_spell = ai_get_best_spell( this, army, m_ai_best_spell_damage );
}

// --------------------------------------------------------
// information about a hero
// give a hero a skill
// --------------------------------------------------------
static t_external_string const k_text_learned_spell( "hero_learned_spell.misc" );

void t_hero::learn_skill( t_skill const& skill, bool show_dialog )
{
	if (m_skills[skill.skill] >= skill.level)
		return;
	if (m_skills[get_primary_skill(skill.skill)] == k_mastery_none
		&& get_primary_skill_count() >= k_primary_skill_limit)
		return;

	// check if we learned any new spells when learning this skill
	std::vector<t_automatic_spell_set> const&	automatic_spells = get_automatic_spells();
	std::vector<t_spell>						new_spells;
	int											i;
	int											j;

	for (i = 0; i < automatic_spells.size(); ++i)
	{
		t_automatic_spell_set const& set = automatic_spells[i];

		// check if this skill applied
		for (j = 0; j < set.skill_requirements.size(); ++j)
		{
			t_skill const& required_skill = set.skill_requirements[j];

			if (skill.skill == required_skill.skill && skill.level >= required_skill.level
				&& get_skill( required_skill.skill ) < required_skill.level)
				break;
		}
		if (j == set.skill_requirements.size())
			continue;
		// check if we've met all the requirements
		for (j = 0; j < set.skill_requirements.size(); ++j)
		{
			t_skill const& required_skill = set.skill_requirements[j];

			if (skill.skill != required_skill.skill && 
				get_skill( required_skill.skill ) < required_skill.level)
				break;
		}
		if (j != set.skill_requirements.size())
			continue;
		// check if we know any of the spells already
		for (j = 0; j < set.spells.size(); ++j)
			if (in_spellbook( set.spells[j] ))
				break;
		if (j < set.spells.size())
			continue;
		new_spells.push_back( set.spells[ random( set.spells.size() ) ] );
	}
	m_skills[skill.skill] = skill.level;
	for ( i = 0; i < new_spells.size(); ++i)
		learn_spell( new_spells[i] );
	if (show_dialog)
		update_class( true );
	if (show_dialog && !new_spells.empty())
	{
		t_counted_ptr<t_basic_dialog>	basic_dialog = new t_basic_dialog;
		std::string						text;
		t_hero_keyword_replacer			replacer;

		replacer.add_hero( this );
		basic_dialog->add_creature( *this, false );
		for (i = 0; i < new_spells.size(); ++i)
		{
			basic_dialog->add_spell( new_spells[i] );
			replacer.add_spell( this, new_spells[i] );
		}
		replacer.add_keyword( "%skill_name", ::get_name( skill ));
		text = replacer( k_text_learned_spell );
		basic_dialog->set_text( text );
		basic_dialog->add_ok_button();
		basic_dialog->open();
		basic_dialog->run_modal();
	}
}

// --------------------------------------------------------
// information about a hero
// get abilities for hero
// --------------------------------------------------------
bool t_hero::has_ability( t_creature_ability ability ) const
{
	if (artifact_gives_ability( *this, ability ))
		return true;
	switch (ability)
	{
		case k_ability_first_strike:
			return m_skills[k_skill_melee] >= k_mastery_expert;
		case k_ability_strikes_twice:
			return m_skills[k_skill_melee] >= k_mastery_grand_master;
		case k_ability_ranged:
			return m_skills[k_skill_archery] >= k_mastery_basic
				|| artifact_gives_ability( *this, k_ability_ranged );
		case k_ability_siege_machine:
			return m_skills[k_skill_archery] >= k_mastery_advanced;
		case k_ability_long_range:
			return m_skills[k_skill_archery] >= k_mastery_expert;
		case k_ability_ranged_first_strike:
			return m_skills[k_skill_archery] >= k_mastery_master;
		case k_ability_normal_melee:
			return true;
		case k_ability_shoots_twice:
			return m_skills[k_skill_archery] >= k_mastery_grand_master;
	};
	return false;
}

// --------------------------------------------------------
// information about a hero
// initialize the hero properties before any objects have
// been placed on the map
// --------------------------------------------------------
bool t_hero::preplacement( t_adventure_map& map, int pass, t_player* owner )
{
	if ( m_named_by_map && pass == 0 )
		map.add_mapped_name( get_name(), this );

	if ( m_portrait_id >= 0 )
	{
		assert( get_class() != k_hero_class_none );
		assert( m_gender != k_gender_unknown );

		if ( pass == 0 )
		{
			// If this hero was given a portrait in the map file then it needs
			// to be initialized on the first pass

			// If the given portrait id is already used, get a generic hero.
			// Otherwise, get the default hero associated with the portrait
			t_default_hero hero_data;
			if ( map.is_hero_portrait_used( m_portrait_id ) )
				hero_data = map.get_generic_hero( m_portrait_id );
			else
				hero_data = get_default_hero( get_default_biography_id_from_portrait_id( m_portrait_id ) );

			initialize( map, hero_data );
		}
		return false;
	}

	// This hero was not given a portrait id in the map file, so initialize it
	// on the second pass, after all of the heroes with portrait ids
	if ( pass < 1 )
		return true; // Return true, indicating that we need initialization on
					 // the next pass

	if ( pass == 1 )
	{
		derandomize( map, owner );
	}

	return false;
}

// --------------------------------------------------------
// Resolve any "random" or "unknown" elements in the 
// hero after reading from map
// --------------------------------------------------------
void t_hero::derandomize( t_adventure_map & map, t_player* owner )
{
	if ( m_portrait_id < 0 )
	{
		if ( get_class() == k_hero_class_none )
		{
			// The hero class is indeterminate, so we need to pick one
			if ( owner != 0 )
			{
				t_town_type alignment = owner->get_alignment();
				assert( alignment >= 0 && alignment < k_town_type_count );

				// Build a set of available hero classes which match the
				// owner's alignment
				std::vector< t_hero_class > available_classes;
				t_hero_class hero_class;
				for (	hero_class = t_hero_class( 0 );
						hero_class < k_hero_class_basic_count;
						enum_incr( hero_class ) )
				{
					if ( ::get_alignment( hero_class ) == alignment )
						available_classes.push_back( hero_class );
				}
				assert( !available_classes.empty() );

				set_class( available_classes[ random( available_classes.size() ) ] );
			}
			else
				set_class( random( k_hero_class_basic_count ) );
		}

		if ( m_gender == k_gender_unknown )
		{
			// The gender is indeterminate, so simply pick one randomly
			m_gender = t_gender( random( 2 ) );
		}

		t_default_hero hero_data;
		{
			std::vector< t_default_hero > default_heroes;
			map.get_unused_heroes( get_class(), is_male() ).swap( default_heroes );
			hero_data = default_heroes[ random( default_heroes.size() ) ];
		}

		initialize( map, hero_data );
	}
}


// --------------------------------------------------------
// information about a hero
// initialize the hero from the default hero data
// --------------------------------------------------------
void t_hero::initialize( t_adventure_map& map, t_default_hero const& hero_data )
{
	set_class( hero_data.hero_class );
	m_use_spellcaster_model = is_spellcaster( hero_data.hero_class );
	m_last_combat_offer = 0;
	if (hero_data.is_male)
		m_gender = k_gender_male;
	else
		m_gender = k_gender_female;

	if ( m_portrait_id < 0 )
		m_portrait_id = hero_data.portrait_id;
	if ( m_biography.empty() )
		m_biography = hero_data.biography;
	if ( m_name.empty() )
		m_name = hero_data.name;
	map.record_hero_usage( hero_data.biography_id, hero_data.portrait_id );

	m_alignment = ::get_alignment( get_class() );

	// If there are no skills set, fill in the default starting skills
	bool any_skills =
			std::find_if(
				&m_skills[ 0 ],
				&m_skills[ k_skill_type_count ],
				std::bind2nd( std::not_equal_to< t_skill_mastery >(), k_mastery_none ) )
		!=	&m_skills[ k_skill_type_count ];

	if ( !any_skills )
	{
		std::vector< t_skill > const & skills = get_starting_skills( get_class() );
		int i;
		for ( i = 0; i < skills.size(); ++i )
			learn_skill( skills[ i ] );
	}

	// Set the experience level
	int base_level = compute_default_experience_level( get_class(), get_skills() );

	if ( m_level == 0 )
	{
		m_level = base_level;
		m_experience = get_experience( m_level );
	}
	else
	{
		m_experience = get_experience( m_level );
		if (base_level < m_level)
			m_level = base_level;
		check_level( map, 0, 0 );
	}

	update_class();

	m_spell_points = get_maximum_spell_points();
	set_raw_adventure_movement( get_raw_adventure_movement() );
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
int t_hero::get_wounds() const
{
	return m_wounds;
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
bool t_hero::is_active() const
{
	return !m_dead && m_wounds < get_hit_points();
}

bool t_hero::is_dead() const
{
	return m_dead;
}


// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------

// --------------------------------------------------------
// information about a hero
// Get the hero's skill level
// --------------------------------------------------------
t_skill_mastery t_hero::get_skill( t_skill_type skill ) const
{
	return m_skills[skill];
}

static t_skill_mastery get_tactics_skill( t_hero const& hero, t_skill_type skill,
										  bool sea_battle )
{
	t_skill_mastery result = hero.get_skill( skill );

	if (sea_battle)
	{
		t_skill_mastery sea_value = hero.get_skill( k_skill_seamanship );

		if (result < sea_value)
			result = sea_value;
	}
	return result;
}


// --------------------------------------------------------
// information about a hero
// Get the effectiveness of the hero's Defense skill (percent bonus)
// --------------------------------------------------------
int	t_hero::get_tactics_defense_bonus( bool sea_battle ) const
{
	return k_tactics_defense_bonus[ get_tactics_skill( *this, k_skill_defense, sea_battle ) + 1];
}

// --------------------------------------------------------
// information about a hero
// Get the effectiveness of the hero's Offsense skill (percent bonus)
// --------------------------------------------------------
int t_hero::get_tactics_offense_bonus( bool sea_battle ) const
{
	return k_tactics_offense_bonus[ get_tactics_skill( *this, k_skill_offense, sea_battle ) + 1];
}

// --------------------------------------------------------
// information about a hero
// Get the effect of the hero's Tactics skill on creatures'
// speed
// --------------------------------------------------------
int t_hero::get_tactics_speed_bonus( bool sea_battle ) const
{
	return k_tactics_speed[get_tactics_skill( *this, k_skill_tactics, sea_battle ) + 1];
}

// --------------------------------------------------------
// information about a hero
// Get the effect of the hero's Leadership skill on creatures'
// movement allowance
// --------------------------------------------------------
int t_hero::get_tactics_move_bonus( bool sea_battle ) const
{
	return k_tactics_move_bonus[ get_tactics_skill( *this, k_skill_tactics, sea_battle ) + 1];
}

// --------------------------------------------------------
// information about a hero
// Get the effect of the hero's Leadership skill on creatures'
// morale
// --------------------------------------------------------
int t_hero::get_leadership_luck_bonus() const
{
	return k_leadership_luck_bonus[ get_skill( k_skill_leadership ) + 1 ];
}

int t_hero::get_leadership_morale_bonus() const
{
	return k_leadership_morale_bonus[m_skills[k_skill_leadership] + 1];
}


// --------------------------------------------------------
// information about a hero
// get bonuses to adventure map movement due to skills or artifacts
// --------------------------------------------------------
int	t_hero::get_army_move_bonus( bool sea_movement ) const
{
	if (!sea_movement)
		return 0;
	if (is_dead())
		return k_boat_movement;

	int					result;
	static int const	k_navigation_bonus[] = { 100, 125, 150, 200, 250, 300 };

	result = k_navigation_bonus[ m_skills[k_skill_seamanship] + 1];
	result += get_artifact_adventure_move_bonus( *this, sea_movement );
	return result * k_boat_movement / 100;
}

float t_hero::get_army_move_multiplier( bool sea_movement ) const
{
	if (sea_movement)
		return 0.0f;
	if (is_dead())
		return 1.0f;

	static int const	k_pathfinding_bonus[] = { 100, 100, 100, 100, 125, 150 };
	int					result;

	result = k_pathfinding_bonus[ m_skills[k_skill_pathfinding] + 1];
	result += get_artifact_adventure_move_bonus( *this, sea_movement );

	return result * 0.01;
}


// --------------------------------------------------------
// information about a hero
// get adventure map movement modifiers to army due to terrain
// --------------------------------------------------------
int t_hero::get_army_move_cost( t_terrain_type terrain ) const
{
	static int const k_pathfinding_value[] = { 0, 25, 50, 100, 100, 100 };

	int result = t_stack_with_backpack::get_army_move_cost( terrain );

	if (result > 100 && !is_dead())
	{
		result -= k_pathfinding_value[m_skills[k_skill_pathfinding] + 1];
		if (result < 100 || has_terrain_mastery( terrain ) 
			|| has_terrain_artifact( *this, terrain ))
			result = 100;
	}
	return result;
}

bool t_hero::is_native_terrain( t_terrain_type terrain ) const
{
	return has_terrain_mastery( terrain ) || has_terrain_artifact( *this, terrain );
}

// --------------------------------------------------------
// information about a hero
// get cost of resurrection, based on level
// --------------------------------------------------------
int t_hero::get_resurrection_cost() const
{
	return m_level * 500 + 500;
}

// --------------------------------------------------------
// information about a hero
// get chance of being stoned.  Units are 1/10th of 1%.
// --------------------------------------------------------
int t_hero::get_stoning_chance() const
{
	return 500 / (m_level + 10);
}

// --------------------------------------------------------
// information about a hero
// iterate over equipped artifacts and see if any accepts the visitor
// --------------------------------------------------------
bool t_hero::accept( t_artifact_effect_visitor& visitor ) const
{
	t_artifact_slot slot;

	for (	slot = t_artifact_slot( 0 );
			slot < k_artifact_slot_hero_count;
			enum_incr( slot ) )
	{
		if (m_artifacts[slot].accept( visitor ))
			return true;
	}
	return m_hero_class_artifact.accept( visitor );
}

// --------------------------------------------------------
// get basic defense ability of hero
// --------------------------------------------------------
float t_hero::get_defense_basic( bool ranged ) const
{
	return 1.0;
}

// --------------------------------------------------------
// information about a hero
// get defense value from artifacts
// --------------------------------------------------------
float t_hero::get_defense_bonus( bool ranged ) const
{
	static float const k_toughness_value[] = { 1.0, 1.5, 2.0, 3.0, 4.0, 6.0 };

	float result = k_toughness_value[ get_skill( k_skill_combat_defense ) +  1 ];

	result += m_bonus[k_stat_defense] * 0.1;
	// note: artifacts deliberately add, rather than multiply, defense
	result = result + get_artifact_armor_value( *this, ranged ) * 0.01f;

	if ( result < 0.5 )
		result = 0.5; // Avoid /0 crashes & doing negative damage

	return result;
}

// --------------------------------------------------------
// get reduction in enemy defense
// --------------------------------------------------------
float t_hero::get_defense_reduction( bool ranged ) const
{
	static float const k_melee_reduction[]  = { 1.0, 1.5, 2.0, 3.0, 4.0, 6.0 };
	static float const k_ranged_reduction[] = { 1.0, 1.0, 1.5, 2.0, 2.0, 2.0 };

	if (ranged)
		return k_ranged_reduction[ get_skill( k_skill_archery ) + 1];
	return k_melee_reduction[ get_skill( k_skill_melee ) + 1 ];
}

// --------------------------------------------------------
// get attack skill
// --------------------------------------------------------
float t_hero::get_offense( bool ranged ) const
{
	static float const k_melee_value[]  = { 1.0f, 1.5f, 2.0f, 3.0f, 4.0f, 4.0f };
	static float const k_ranged_value[] = { 0.7f, 1.0f, 1.5f, 2.0f, 3.0f, 3.0f };
	float				result;

	if (ranged)
		result = k_ranged_value[ get_skill( k_skill_archery ) + 1 ];
	else
		result = k_melee_value[ get_skill( k_skill_melee ) + 1 ];
	// note: artifacts deliberately add, rather than multiply, offense

	result += get_artifact_damage_modifier( *this, ranged ) / 100.0f;
	if (ranged)
		result += m_bonus[k_stat_attack] * 0.07;
	else
		result += m_bonus[k_stat_attack] * 0.1;

	if ( result < 0.5 )
		result = 0.5; // Avoid /0 crashes & doing negative damage

	return result;
}

// --------------------------------------------------------
// information about a hero
// get luck 
// --------------------------------------------------------
int t_hero::get_luck() const
{
	return t_stack_with_backpack::get_luck();
}

// --------------------------------------------------------
// information about a hero
// get morale
// --------------------------------------------------------
int t_hero::get_morale( t_creature_array const& army ) const
{
	return t_stack_with_backpack::get_morale( army );
}

int	t_hero::get_skill_power( t_skill const& skill ) const
{
	static int const k_charm_percentage[]			= { 0, 15, 20, 25, 30, 35 };
	static int const k_necromancy_percentage[]		= { 0, 10, 15, 20, 25, 30 };
	static int const k_summoning_power[]			= { 0, 10, 20, 30, 40, 50 };
	static int const k_resurrection_percentage[]	= { 0, 20, 30, 40, 45, 50 };
	static int const k_nobility_bonus[]				= { 0, 10, 20, 30, 40, 50 };
	int				 result = 0;
	int				 level = skill.level + 1;

	switch (skill.skill)
	{
		case k_skill_charm:
			result = k_charm_percentage[level];
			break;

		case k_skill_diplomacy:
			result = k_charm_percentage[level] * 2;
			break;

		case k_skill_necromancy:
			result = k_necromancy_percentage[level];
			break;

		case k_skill_resurrection:
			result = k_resurrection_percentage[level];
			break;

		case k_skill_recruitment:
			result = k_summoning_power[level];
			result += get_artifact_skill_modifier( *this, skill.skill ) * 10;
			return result;

		case k_skill_nobility:
			result = k_nobility_bonus[level];
			break;

		default:
			assert( false );
	};

	result += get_artifact_skill_modifier( *this, skill.skill );
	return result;
}

// --------------------------------------------------------
// information about a hero
// get power level of a skill
// --------------------------------------------------------
int	t_hero::get_skill_power( t_skill_type skill ) const
{
	return get_skill_power( t_skill( skill, m_skills[skill] ) );
}

// --------------------------------------------------------
// --------------------------------------------------------
void t_hero::execute_script(
	t_built_in_event_id				event_id,
	t_script_context_hero const&	context )
{
	assert( event_id >= 0 && event_id < k_built_in_event_count );

	t_built_in_event_ptr event_ptr = m_built_in_events[ event_id ];
	if (!event_ptr->ownership_test(context.army_owner)) return;
	event_ptr->execute_effects(context.map->get_current_player(), context.army->get_adventure_frame());

	t_built_in_event::t_script const & script = event_ptr->get_script();

	script.execute( context );
	if( context.map->is_game_over() )
		return;

	if (context.opposing_army) 
		post_execute_validate(context.opposing_army);
	if (context.army)
		post_execute_validate(context.army);
		
	if ( script.pending_removal() ) {
		script.clear_removal();
		event_ptr = new t_built_in_event;
	}
}


// -----------------------------------------------------------
// -----------------------------------------------------------
bool t_hero::process_timed_events( t_adventure_map& map, t_creature_array* array, t_player* player, t_adv_map_point const* position, bool start_of_day )
{
	bool result = false;

	int size = m_timed_events.size();
	for (int i = 0; i < size; i++) {
		t_timed_event_ptr event = m_timed_events[i];

		if ( event->is_active_on_day(map.get_day()) && (&map.get_current_player() == player)
			&& event->ownership_test(player) 
			&& (start_of_day || !event->has_executed()) )
		{
			result = true;

			event->set_execution_flag();
			event->execute_effects(map.get_current_player(), array->get_adventure_frame());

			t_timed_event::t_script const & script = event->get_script();
			script.execute(t_script_context_hero(&map, this, array, player, NULL, NULL, position));
			if( map.is_game_over() )
				return false;

			map.get_map_window()->get_frame()->update();
			post_execute_validate(array);
			
			if ( script.pending_removal() ) {
				script.clear_removal();
				event = new t_ownable_timed_event;
			}
		}
		else 
			if (start_of_day)
				event->clear_execution_flag();
	}

	return result;
}

// -----------------------------------------------------------
// -----------------------------------------------------------
bool t_hero::process_continuous_events( t_adventure_map& map, t_creature_array* army, t_player* player, t_adv_map_point const* position )
{
	bool result = false;

	int size = m_continuous_events.size();
	for (int i = 0; i < size; i++) {
		t_continuous_event_ptr event = m_continuous_events[i];
		if (( !event->get_run_only_during_owners_turn() || &map.get_current_player() == player )
			&& event->ownership_test(player) )
		{
			result = true;

			t_continuous_event::t_script const & script = event->get_script();
			script.execute(t_script_context_hero(&map, this, army, player, NULL, NULL, position));
			if( map.is_game_over() )
				return false;

			map.get_map_window()->get_frame()->update();
			post_execute_validate(army);

			if ( script.pending_removal() ) {
				script.clear_removal();
				event = new t_ownable_continuous_event;
			}
		}
	}

	return result;
}

// -----------------------------------------------------------
// -----------------------------------------------------------
bool t_hero::process_triggerable_events( t_adventure_map& map, std::string const& name, t_creature_array* army, t_player* player, t_adv_map_point const* position )
{
	bool result = false;

	int size = m_triggerable_events.size();
	for (int i = 0; i < size; i++) {
		t_triggerable_event_ptr event = m_triggerable_events[i];
		if (( string_insensitive_compare(event->get_name(), name) == 0 ) 
			&& event->ownership_test(player) )
		{
			result = true;

			event->execute_effects(map.get_current_player(), army->get_adventure_frame());

			t_triggerable_event::t_script const & script = event->get_script();
			script.execute(t_script_context_hero(&map, this, army, player, NULL, NULL, position));
			if( map.is_game_over() )
				return false;
			
			map.get_map_window()->get_frame()->update();
			post_execute_validate(army);

			if ( script.pending_removal() ) {
				script.clear_removal();
				event = new t_ownable_triggerable_event;
			}
		}
	}

	return result;
}

// -----------------------------------------------------------
// -----------------------------------------------------------
void t_hero::read_postplacement( t_adventure_map& map )
{
	if (m_named_by_map)
		map.add_mapped_name( get_name(), this );

	assert( m_nobility_town_position != k_nobility_town_position_not_in_use );

	if ( m_nobility_town_id != k_invalid_global_id )
	{
		t_adventure_object * object = map.get_object_by_global_id( m_nobility_town_id );

		t_abstract_town *town = dynamic_cast< t_abstract_town * >( object );

		assert( town );

		if (m_nobility_town != NULL)
			m_nobility_town->remove_hero_from_nobility_list( this );

		m_nobility_town = town;

		if ( town )
			town->add_hero_to_nobility_list( this );
	}
	else if ( m_nobility_town_position != k_nobility_town_position_none )
	{
		const t_adventure_tile &tile = map[m_nobility_town_position];
		const std::vector<int>& triggers = tile.get_triggers(false);
		for (std::vector<int>::const_iterator trigger_iterator = triggers.begin(); trigger_iterator != triggers.end(); trigger_iterator++)
		{
			t_abstract_adv_object &object = map.get_object(*trigger_iterator);

			t_abstract_town *town = dynamic_cast<t_abstract_town *>(&object);
			
			if (town)
			{
				// Don't call set_nobility_town, it messes up our priority
				if (m_nobility_town != NULL)
					m_nobility_town->remove_hero_from_nobility_list(this);

				m_nobility_town = town;

				town->add_hero_to_nobility_list(this);
				break;
			}
		}

		assert(m_nobility_town != NULL);
	}
	else
	{
		clear_nobility_town();
	}
	
	m_nobility_town_position = k_nobility_town_position_not_in_use;
	m_nobility_town_id = k_invalid_global_id;

	t_stack_with_backpack::read_postplacement(map);
}

// -----------------------------------------------------------
// -----------------------------------------------------------
void t_hero::clear_nobility_town()
{
	if (m_nobility_town != NULL)
	{
		m_nobility_town->remove_hero_from_nobility_list(this);
		m_nobility_town = NULL;
		m_nobility_priority = 0;
	}
}

// -----------------------------------------------------------
// The hero comes to town
// -----------------------------------------------------------
void t_hero::visit_town(t_abstract_town *town, t_creature_array const & owning_array )
{
	// If we are not linked to a town, might as well link to this
	// one. If we could be a better governer than the current
	// governer, and we are not governing another town, switch
	// to this one
	bool switch_towns;

	if ( m_nobility_town_position != k_nobility_town_position_not_in_use || m_nobility_town_id != k_invalid_global_id )
	{
		// Still initializing after read, don't interfere
		switch_towns = false;
	}
	else if ( town->get_owner_number() != owning_array.get_owner_number() )
	{
		// Can't govern another player's town
		switch_towns = false;
	}
	else if ( m_nobility_town.get() == NULL )
	{
		switch_towns = true;
	}
	else if ( m_nobility_town.get() == town )
	{
		switch_towns = false;
	}
	else if ( m_nobility_town->get_best_nobility_hero() == this )
	{
		switch_towns = false;
	}
	else if ( town->get_best_nobility_hero() == NULL )
	{
		switch_towns = true;
	}
	else if ( town->get_best_nobility_hero()->get_skill_power( k_skill_nobility ) < get_skill_power( k_skill_nobility ) )
	{
		switch_towns = true;
	}
	else
	{
		switch_towns = false;
	}

	if ( switch_towns )
	{
		set_nobility_town( town );
		lower_nobility_priority(); // Automatic linkage is based on senority
	}
}


// -----------------------------------------------------------
// Hero can now govern this town...
// -----------------------------------------------------------
void t_hero::set_nobility_town(t_abstract_town *town)
{
	if (m_nobility_town.get() != town)
	{
		m_nobility_priority = 0;

		if (m_nobility_town != NULL)
			m_nobility_town->remove_hero_from_nobility_list(this);
	
		m_nobility_town = town;

		town->add_hero_to_nobility_list(this);
	}
}

// -----------------------------------------------------------
// Make this hero first in line for governership of the current
// nobility town. This doesn't allow him to take over a town
// from a hero of a higher nobility, but it does mean that 
// he will be set as governer of the town in preference to any
// other hero of the same nobility
// -----------------------------------------------------------
void t_hero::raise_nobility_priority()
{
	assert( m_nobility_town );

	if ( m_nobility_town )
	{
		m_nobility_priority = 0; // Don't just increment priority

		m_nobility_priority = m_nobility_town->get_highest_nobility_priority() + 1;
	}
}

// -----------------------------------------------------------
// Make this hero last in line for governership of the current
// nobility town. This doesn't allow another hero with lower 
// nobility to take over a town from this hero, but it does mean 
// that any other hero of the same skill will be set as governer
// in preference to this hero.
// -----------------------------------------------------------
void t_hero::lower_nobility_priority()
{
	assert( m_nobility_town );

	if ( m_nobility_town )
	{
		m_nobility_priority = 0; // Don't just decrement priority

		m_nobility_priority = m_nobility_town->get_lowest_nobility_priority() - 1;
	}
}

// -----------------------------------------------------------
// kill / resurrect hero.
// -----------------------------------------------------------
void t_hero::set_dead( bool arg )
{
	m_dead = arg;
}

// --------------------------------------------------------
// --------------------------------------------------------
t_creature_stack::t_clone_ptr t_hero::clone() const
{
	t_hero* hero = new t_hero( *this );
	return hero;
}

// --------------------------------------------------------
// --------------------------------------------------------
int t_hero::get_magic_resistance( t_player const* owner, t_spell spell ) const
{
	static int const k_magic_resistance[] = { 0, 30, 50, 70, 80, 100 };

	t_skill_mastery mastery = get_skill( k_skill_magic_resistance );
	int result = k_magic_resistance[ mastery + 1 ];

	int bonus = get_artifact_magic_resistance( *this );

	result += ( 100 - result ) * bonus / 100;

	if ( owner != 0 )
	{
		bonus = owner->get_magic_dampener_bonus();
		result += ( 100 - result ) * bonus / 100;
	}

	return result;
}

// --------------------------------------------------------
// information about a hero
// --------------------------------------------------------
void t_hero::store( t_hero_carryover_data & carryover_data ) const
{
	carryover_data.set_alignment( m_alignment );
	carryover_data.set_name( m_name );
	carryover_data.set_is_named_by_map( m_named_by_map );
	carryover_data.set_biography( m_biography );
	carryover_data.set_biography_id( m_biography_id );
	carryover_data.set_portrait_id( m_portrait_id );
	carryover_data.set_experience( m_experience );
	carryover_data.set_class( get_class() );
	carryover_data.set_is_male( m_gender == k_gender_male );
	carryover_data.set_spellbook( m_spellbook );
	carryover_data.set_use_spellcaster_model( m_use_spellcaster_model );

	t_artifact_slot slot;
	for ( slot = t_artifact_slot( 0 ); slot < k_artifact_slot_hero_count; enum_incr( slot ) )
		carryover_data.set_artifact( slot, m_artifacts[ slot ] );

	int backpack_size = get_backpack_count();
	t_hero_carryover_data::t_backpack backpack;
	backpack.reserve( backpack_size );
	int i;
	for ( i = 0; i < backpack_size; ++i )
		backpack.push_back( get_backpack_slot( i ) );
	carryover_data.set_backpack( backpack );

	t_skill_type skill;
	for ( skill = t_skill_type( 0 ); skill < k_skill_type_count; enum_incr( skill ) )
		carryover_data.set_mastery( skill, m_skills[ skill ] );

	int bonuses[ k_stat_type_count ];
	std::copy( m_bonus, m_bonus + k_stat_type_count, bonuses );
	subtract_temporary_bonuses( bonuses, m_bonus_array );
	subtract_temporary_bonuses( bonuses, m_timed_bonus_array );
	carryover_data.set_bonuses( bonuses );
}

// --------------------------------------------------------
// --------------------------------------------------------
void t_hero::set_built_in_events( t_built_in_event_list const & new_events )
{
	std::copy(
		new_events,
		new_events + ELEMENTS_OF( new_events ),
		m_built_in_events );
}

// --------------------------------------------------------
// --------------------------------------------------------
void t_hero::take_continuous_events( t_continuous_event_list & new_events )
{
	m_continuous_events.swap( new_events );
}

// --------------------------------------------------------
// --------------------------------------------------------
void t_hero::take_timed_events( t_timed_event_list & new_events )
{
	m_timed_events.swap( new_events );
}

// --------------------------------------------------------
// --------------------------------------------------------
void t_hero::take_triggerable_events( t_triggerable_event_list & new_events )
{
	m_triggerable_events.swap( new_events );
}

// --------------------------------------------------------
// Redistribute artifacts from this hero to (other) 
// creatures in army. If the artifacts can't go anywhere,
// put them in leftovers.
// --------------------------------------------------------
void t_hero::redistribute_artifacts( t_creature_array & army, t_artifact_list & leftovers )
{
	// Moved equipped artifacts into backpack, then let t_stack_with_backpack worry
	// about the actual distribution
	t_artifact_slot slot;

	for ( slot = t_artifact_slot( 0 ); slot < k_artifact_slot_hero_count; enum_incr( slot ) ) 
	{
		t_artifact artifact = remove( slot );

		if ( artifact.get_icon() != k_artifact_none )
		{
			add( artifact, false );
		}

		assert( get_artifact( slot ).get_icon() == k_artifact_none );
	}

	t_stack_with_backpack::redistribute_artifacts( army, leftovers );
}

// --------------------------------------------------------
// --------------------------------------------------------
t_skill_mastery t_hero::get_anti_stealth_level() const
{
	t_skill_mastery result = get_skill( k_skill_scouting );

	if (is_spell_active( k_spell_visions ) && result < k_mastery_master)
		result = k_mastery_master;
	return result;
}	

t_skill_mastery t_hero::get_stealth_level() const
{
	return get_skill( k_skill_stealth );
}

// --------------------------------------------------------
// mark a particular army as eluded.
// --------------------------------------------------------
bool t_hero::mark_eluded( t_army const& army )
{
	if (army.get_owner() != 0)
		return false;

	int id = army.get_global_id();

	return m_eluded_army_ids.insert( id ).second;
}

bool t_hero::was_eluded( t_army const& army ) const
{
	int id = army.get_global_id();

	return m_eluded_army_ids.count( id ) > 0;
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
std::string	t_hero::get_right_click_text() const
{
	return get_class_description();
}

// -----------------------------------------------------------------------
// refresh the state of the hero's artifact spell list
// -----------------------------------------------------------------------
void t_hero::update_artifact_spells()
{
	m_book_spells.reset();
	m_scroll_spells.reset();
	set_artifact_spells( *this );
}

